<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
    <title>图片合成工具</title>
    <style>
        /* 加载动画样式 */
        .loading-spinner {
            border: 4px solid rgba(0, 0, 0, 0.1);
            border-left-color: #007BFF;
            border-radius: 50%;
            width: 24px;
            height: 24px;
            animation: spin 1s linear infinite;
            margin: 0 auto;
        }

        @keyframes spin {
            to {
                transform: rotate(360deg);
            }
        }

        /* 调整合成后图片的显示样式 */
        #mergedImageContainer img, #previewContainer img {
            max-width: 80%; /* 限制图片最大宽度 */
            max-height: 60vh; /* 限制图片最大高度 */
            display: block; /* 让图片在容器中居中 */
            margin: 0 auto;
        }

        /* 下载提示样式 */
        #downloadPrompt {
            text-align: center;
            margin-top: 10px;
            color: #333;
        }

        /* 选项样式 */
        .option-group {
            margin-bottom: 16px;
            padding: 12px;
            border: 1px solid #e5e7eb;
            border-radius: 8px;
            background-color: #f9fafb;
        }

        .option-title {
            font-weight: 600;
            margin-bottom: 8px;
            color: #374151;
        }

        .radio-group, .checkbox-group {
            display: flex;
            flex-direction: column;
        }

        .radio-label, .checkbox-label {
            display: flex;
            align-items: center;
            cursor: pointer;
        }

        .radio-label input, .checkbox-label input {
            margin-right: 6px;
        }
    </style>
</head>

<body class="flex flex-col items-center min-h-screen">
    <div class="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
        <h1 class="text-2xl font-bold text-center text-gray-800 mb-6">图片合成工具</h1>
        <div class="mb-6">
            <label for="imageInput" class="block text-gray-700 font-medium mb-2">选择图片</label>
            <input type="file" id="imageInput" accept="image/*" multiple
                class="border border-gray-300 p-2 rounded-md w-full focus:outline-none focus:border-blue-500">
        </div>

        <!-- 背景选项 -->
        <div class="option-group">
            <div class="option-title">背景选项</div>
            <div class="radio-group">
                <label class="radio-label">
                    <input type="radio" name="background" value="black" checked>
                    黑色背景
                </label>
                <label class="radio-label">
                    <input type="radio" name="background" value="white">
                    白色背景
                </label>
            </div>
        </div>

        <!-- 反色选项 -->
        <div class="option-group">
            <div class="option-title">图片处理</div>
            <div class="checkbox-group">
                <label class="checkbox-label">
                    <input type="checkbox" id="invertColors">
                    开启图片反色
                </label>
            </div>
        </div>

        <!-- 分步预览容器 -->
        <div id="previewSteps" class="mt-6 hidden">
            <h2 class="text-lg font-bold text-gray-700 mb-2 text-center">合成过程</h2>
            <div id="stepPreviewContainer" class="flex overflow-x-auto p-2 bg-gray-50 rounded-lg" style="max-width: 800px;">
                <!-- 步骤预览将通过JavaScript动态添加 -->
            </div>
        </div>

        <!-- 主预览区域 -->
        <div id="previewContainer" class="hidden mt-6 text-center">
            <h2 class="text-lg font-bold text-gray-700 mb-2">当前状态预览</h2>
            <img id="previewImage" src="" alt="预览图片" class="mb-4">
            <div id="progressText" class="text-sm text-gray-600"></div>
            <p id="downloadPrompt">点击下面的链接下载合成后的图片：</p>
            <a id="downloadLink" href="#" download="merged_image.png" class="inline-block mt-2 bg-green-500 hover:bg-green-600 text-white py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-opacity-50 transition duration-300 ease-in-out">下载图片</a>
        </div>
    </div>

    <script>
        // 获取DOM元素
        const imageInput = document.getElementById('imageInput');
        const downloadPrompt = document.getElementById('downloadPrompt');
        const downloadLink = document.getElementById('downloadLink');
        const previewContainer = document.getElementById('previewContainer');
        const previewSteps = document.getElementById('previewSteps');
        const previewImage = document.getElementById('previewImage');
        const backgroundOptions = document.getElementsByName('background');
        const invertColorsCheckbox = document.getElementById('invertColors');

        /**
         * 加载图片函数 - 将File对象转换为Image对象
         * @param {File} file - 要加载的图片文件
         * @returns {Promise<Image>} - 返回加载完成的Image对象
         */
        async function loadImage(file) {
            return new Promise((resolve, reject) => {
                const img = new Image();
                const objectURL = URL.createObjectURL(file);
                img.src = objectURL;

                // 增加加载完成后的清理操作
                const cleanup = () => {
                    URL.revokeObjectURL(objectURL); // 释放URL对象
                    img.onload = img.onerror = null; // 移除事件监听
                };

                img.onload = () => {
                    cleanup();
                    resolve(img);
                };

                img.onerror = (err) => {
                    cleanup();
                    console.error("图片加载失败:", err);
                    reject(new Error('无法加载图片，请检查文件格式（支持JPG/PNG等常见格式）'));
                };

                // 增加移动端HEIC格式处理提示
                if (file.name.toLowerCase().endsWith('.heic')) {
                    reject(new Error('检测到HEIC格式图片，请转换为JPG/PNG后再上传'));
                }
            });
        }

        /**
         * 自动检测图片背景颜色函数
         * @param {File} file - 要分析的图片文件
         * @returns {Promise<string>} - 返回检测到的背景颜色('black'或'white')
         */
        async function detectBackgroundColor(file) {
            try {
                const img = await loadImage(file);
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');

                // 设置画布大小与图片相同
                canvas.width = img.width;
                canvas.height = img.height;

                // 绘制图片到画布
                ctx.drawImage(img, 0, 0);

                // 获取图片像素数据
                const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                const data = imageData.data;

                let blackPixels = 0;
                let whitePixels = 0;
                const threshold = 128; // 亮度阈值，小于此值视为暗色，大于视为亮色

                // 分析像素
                for (let i = 0; i < data.length; i += 4) {
                    // 计算像素亮度 (简化的灰度计算)
                    const brightness = (data[i] + data[i + 1] + data[i + 2]) / 3;

                    // 统计黑白像素数量
                    if (brightness < threshold) {
                        blackPixels++;
                    } else {
                        whitePixels++;
                    }
                }

                // 返回主要背景颜色
                return blackPixels > whitePixels ? 'black' : 'white';
            } catch (error) {
                console.error('检测背景颜色出错:', error);
                return 'black'; // 默认返回黑色背景
            }
        }

        /**
         * 图片反色处理函数
         * @param {HTMLCanvasElement} canvas - 要处理的画布元素
         * @returns {HTMLCanvasElement} - 返回处理后的画布
         */
        function invertImageColors(canvas) {
            const ctx = canvas.getContext('2d');
            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            const data = imageData.data;

            for (let i = 0; i < data.length; i += 4) {
                // 反转RGB值（不改变Alpha通道）
                data[i] = 255 - data[i];         // R
                data[i + 1] = 255 - data[i + 1]; // G
                data[i + 2] = 255 - data[i + 2]; // B
            }

            ctx.putImageData(imageData, 0, 0);
            return canvas;
        }

        /**
         * 获取当前选择的背景模式
         * @returns {string} - 返回当前选择的背景模式('black'或'white')
         */
        function getBackgroundMode() {
            for (const option of backgroundOptions) {
                if (option.checked) {
                    return option.value;
                }
            }
            return 'black'; // 默认为黑色背景
        }

        /**
         * 检测并处理图片重叠区域函数
         * @param {ImageData} baseImageData - 基础图像数据
         * @param {ImageData} newImageData - 新图像数据
         * @param {string} backgroundMode - 背景模式('black'或'white')
         * @returns {ImageData} - 返回处理后的图像数据
         */
        function detectOverlap(baseImageData, newImageData, backgroundMode) {
            const baseData = baseImageData.data;
            const newData = newImageData.data;
            const result = new Uint8ClampedArray(newData.length);

            for (let i = 0; i < newData.length; i += 4) {
                // 获取两个图像对应位置的像素值
                const baseR = baseData[i];
                const baseG = baseData[i + 1];
                const baseB = baseData[i + 2];
                const baseA = baseData[i + 3];

                const newR = newData[i];
                const newG = newData[i + 1];
                const newB = newData[i + 2];
                const newA = newData[i + 3];

                if (backgroundMode === 'black') {
                    // 黑色背景模式：取两个像素的最大值（类似滤色效果）
                    result[i] = Math.max(baseR, newR);
                    result[i + 1] = Math.max(baseG, newG);
                    result[i + 2] = Math.max(baseB, newB);
                    result[i + 3] = 255;
                } else {
                    // 白色背景模式：取两个像素的最小值（类似正片叠底效果）
                    result[i] = Math.min(baseR, newR);
                    result[i + 1] = Math.min(baseG, newG);
                    result[i + 2] = Math.min(baseB, newB);
                    result[i + 3] = 255;
                }
            }

            return new ImageData(result, newImageData.width, newImageData.height);
        }

        /**
         * 添加分步预览功能
         * @param {number} stepNumber - 当前步骤编号
         * @param {string} dataURL - 图片的Data URL
         */
        function addStepPreview(stepNumber, dataURL) {
            const container = document.getElementById('stepPreviewContainer');
            
            const stepDiv = document.createElement('div');
            stepDiv.className = 'flex-shrink-0 mx-2 cursor-pointer transition-transform hover:scale-105';
            stepDiv.innerHTML = `
                <div class="bg-white p-2 rounded-lg shadow-md">
                    <div class="text-xs text-gray-600 mb-1">步骤 ${stepNumber}</div>
                    <img src="${dataURL}" class="h-20 w-20 object-contain"
                         onclick="document.getElementById('previewImage').src = this.src">
                </div>
            `;
            container.appendChild(stepDiv);
            container.scrollLeft = container.scrollWidth; // 自动滚动到最新添加的预览
        }

        /**
         * 更新进度显示
         * @param {number} current - 当前完成的步骤
         * @param {number} total - 总步骤数
         */
        function updateProgress(current, total) {
            document.getElementById('progressText').textContent = 
                `已完成步骤 ${current}/${total} (${Math.round((current/total)*100)}%)`;
        }

        /**
         * 合成多张图片
         * @param {FileList} files - 要合成的图片文件列表
         * @param {string} backgroundMode - 背景模式('black'或'white')
         * @returns {HTMLCanvasElement} - 返回合成后的画布
         */
        async function mergeImages(files, backgroundMode) {
            if (files.length === 0) {
                throw new Error('请选择要合成的图片！');
            }

            // 检查第一张图片是否能正常加载
            const firstFile = files[0];
            const firstImage = await loadImage(firstFile);
            if (!firstImage) {
                throw new Error('第一张图片加载失败');
            }

            const canvas = document.createElement('canvas');
            canvas.width = firstImage.width;
            canvas.height = firstImage.height;
            const ctx = canvas.getContext('2d');

            // 绘制第一张图片
            ctx.drawImage(firstImage, 0, 0, canvas.width, canvas.height);

            // 获取第一张图片的像素数据，用于后续重叠检测
            let baseImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

            // 添加初始状态预览
            addStepPreview(1, canvas.toDataURL('image/png'));
            updateProgress(0, files.length);
            previewSteps.classList.remove('hidden');

            // 合成其他图片，全部缩放到第一张图片的尺寸
            for (let i = 1; i < files.length; i++) {
                const file = files[i];
                try {
                    const image = await loadImage(file);

                    // 创建临时画布处理当前图片
                    const tempCanvas = document.createElement('canvas');
                    tempCanvas.width = canvas.width;
                    tempCanvas.height = canvas.height;
                    const tempCtx = tempCanvas.getContext('2d');

                    // 绘制当前图片到临时画布
                    tempCtx.drawImage(image, 0, 0, tempCanvas.width, tempCanvas.height);

                    // 获取当前图片的像素数据
                    const newImageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);

                    // 检测并处理重叠区域
                    const processedImageData = detectOverlap(baseImageData, newImageData, backgroundMode);

                    // 将处理后的图像数据绘制回临时画布
                    tempCtx.putImageData(processedImageData, 0, 0);       

                    // 将处理后的图像合成到主画布
                    ctx.drawImage(tempCanvas, 0, 0);

                    // 生成步骤快照
                    addStepPreview(i+1, canvas.toDataURL('image/png'));
                    updateProgress(i+1, files.length+1);

                    // 更新基础图像数据，用于下一张图片的重叠检测
                    baseImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

                } catch (imgErr) {
                    console.error(`第 ${i + 1} 张图片加载或处理出错:`, imgErr);
                }
            }

            addStepPreview(files.length + 1, canvas.toDataURL('image/png'));
            updateProgress(files.length + 1, files.length+1);

            return canvas;
        }

        // 存储最后一次合成的画布和设置，用于快速应用反色效果
        let lastMergedCanvas = null;
        let lastBackgroundMode = null;
        let needRecompose = true;

        /**
         * 更新预览图片
         * 根据当前选择的图片和设置生成合成预览
         */
        async function updatePreview() {
            const files = imageInput.files;
            if (files.length === 0) {
                previewContainer.classList.add('hidden');
                previewSteps.classList.add('hidden');
                lastMergedCanvas = null;
                lastBackgroundMode = null;
                needRecompose = true;
                return;
            }

            const shouldInvertColors = invertColorsCheckbox.checked;
            const backgroundMode = getBackgroundMode();

            // 如果已有合成结果，且背景模式没有变化（只是反色选项变化），则直接应用反色效果
            if (lastMergedCanvas && backgroundMode === lastBackgroundMode && !needRecompose) {
                // 创建临时画布用于应用反色效果
                const tempCanvas = document.createElement('canvas');
                tempCanvas.width = lastMergedCanvas.width;
                tempCanvas.height = lastMergedCanvas.height;
                const tempCtx = tempCanvas.getContext('2d');
                tempCtx.drawImage(lastMergedCanvas, 0, 0);

                // 应用反色效果
                if (shouldInvertColors) {
                    invertImageColors(tempCanvas);
                }

                // 更新预览和下载链接
                previewImage.src = tempCanvas.toDataURL('image/png');
                downloadLink.href = tempCanvas.toDataURL('image/png');
                return;
            }

            // 禁用选项，防止处理过程中用户修改设置
            invertColorsCheckbox.disabled = true;
            backgroundOptions[0].disabled = true;
            backgroundOptions[1].disabled = true;

            try {
                // 执行图片合成
                const canvas = await mergeImages(files, backgroundMode);
                
                // 保存合成结果
                lastMergedCanvas = document.createElement('canvas');
                lastMergedCanvas.width = canvas.width;
                lastMergedCanvas.height = canvas.height;
                lastMergedCanvas.getContext('2d').drawImage(canvas, 0, 0);
                
                // 记录当前背景模式
                lastBackgroundMode = backgroundMode;
                needRecompose = false;
                
                // 应用反色效果（如果需要）
                if (shouldInvertColors) {
                    invertImageColors(canvas);
                }
                
                // 更新预览和下载链接
                previewImage.src = canvas.toDataURL('image/png');
                downloadLink.href = canvas.toDataURL('image/png');
                previewContainer.classList.remove('hidden');

                // 重新启用选项
                invertColorsCheckbox.disabled = false;
                backgroundOptions[0].disabled = false;
                backgroundOptions[1].disabled = false;
            } catch (error) {
                console.error("预览生成出错:", error);
                previewContainer.classList.add('hidden');
            }
        }

        /**
         * 自动检测并设置背景选项
         * 分析第一张图片的背景颜色并自动选择合适的背景模式
         */
        async function autoDetectAndSetBackground() {
            const files = imageInput.files;
            if (files.length === 0) return;

            try {
                // 使用第一张图片检测背景颜色
                const detectedColor = await detectBackgroundColor(files[0]);

                // 设置对应的背景选项
                backgroundOptions.forEach(option => {
                    if (option.value === detectedColor) {
                        option.checked = true;
                    }
                });

                console.log(`自动检测到背景颜色: ${detectedColor}`);
            } catch (error) {
                console.error("自动检测背景颜色出错:", error);
            }
        }

        // 事件监听设置

        // 监听选项变化，更新预览
        backgroundOptions.forEach(option => {
            option.addEventListener('change', () => {
                // 背景选项变化需要重新合成
                needRecompose = true;
                updatePreview();
            });
        });
        
        // 反色选项变化时直接应用反色效果，不需要重新合成
        invertColorsCheckbox.addEventListener('change', updatePreview);
        
        // 图片输入变化时需要重新合成
        imageInput.addEventListener('change', async () => {
            needRecompose = true;
            await autoDetectAndSetBackground();
            updatePreview();
        });

        // 初始化预览（如果已选择图片）
        if (imageInput.files.length > 0) {
            updatePreview();
        }
    </script>
</body>

</html>